spring-retry 破坑

使用场景

A接口 -> B接口 调用失败,重试、重试失败降级处理;
如:实现第三方的app Push推送;
如:消息系统推送消息给订阅方;

pom.xml

项目基于springboot,在根pom.xml中引用,所以这里没有<version>,以springboot中的版本为准;
项目根pom.xml

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath/>
</parent>

项目modepom.xml

1
2
3
4
5
6
7
8
9
<!-- spring-retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

spring-retry知识

1
2
3
4
5
6
@Retryable(value= {RemoteAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 1000l,multiplier = 1))
public void call(String param){
}
@Recover
public void recover(RemoteAccessException e) {
}
  • @Retryable
    value选择需要重试的异常,可指定多个;
    maxAttempts 最大重试次数,超过则调用@Recover指定的方法(根据异常类型)
    delay 每次重试的间隔时间
  • @Recover
    当超过重试次数,则调用此注解的方法(根据异常类型)

使用原则:不要全部异常都进行重试,要有选择性的根据异常重试;

两种使用方式

我目前使用的第一种方式,因为业务比较简单;

1、@Retryable和@Recover在同一个类中

@EnableRetry启动配置

1
2
3
4
@Configuration
@EnableRetry
class RetryConfig{
}

业务类RemoteService.java

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class RemoteService {
@Retryable(value= {RemoteAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 1000l,multiplier = 1))
public void call(String param){
System.out.println("do something...");
throw new RemoteAccessException("RPC调用异常");
}
@Recover
public void recover(RemoteAccessException e) {
System.out.println("recover====>"+e.getMessage());
}
}

约束:
1、@Retryable 和 @Recover必须在同一个类中
2、这个类必须是受spring管理的bean
3、方法必须是public

2、拦截器

@EnableRetry启动配置,且加上拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@ConditionalOnProperty(prefix = "mq.consumer.callback", name = {"retry-times","retry-delay-inMilliseconds"} ,matchIfMissing = false)
@EnableRetry
public class RetryConfig{
@Autowired
private ConsumerProperties consumerProperties;

//每一个业务方法对应一个拦截器和自定义recover
@Bean
@ConditionalOnMissingBean(name = "retryInterceptor")
public RetryOperationsInterceptor retryInterceptor() {
return RetryInterceptorBuilder
.stateless().recoverer(new CustomMessageRecover())
.backOffOptions(0L,1D, consumerProperties.getCallback().getRetryDelayInMilliseconds())
.maxAttempts(consumerProperties.getCallback().getRetryTimes()).build();
}
static class CustomMessageRecover implements MethodInvocationRecoverer<Void> {
@Override
public Void recover(Object[] args, Throwable cause) {
System.out.println("IN THE RECOVER ZONE!!!");
return null;
}
}
}

业务类RemoteService.java

1
2
3
4
5
6
7
8
@Service
public class RemoteService {
@Retryable(value= {RemoteAccessException.class},interceptor = "retryInterceptor")
public void call(String param){
System.out.println("do something...");
throw new RemoteAccessException("RPC调用异常");
}
}

注意到没?@Recover注解的方法不在里面了;已经由拦截器代理到CustomMessageRecover类

------ 本文结束 感谢您的阅读 ------